/**
 * 矩阵与向量运算
 * @see https://github.com/likaia/JavaScript-test/blob/master/src/LinearAlgebra/lib/Matrix.ts
 */
// 向量类
class Vector {
    constructor(private list: number[] = []) {}

    // 获取向量维度
    getDimension(): number {
        return this.list.length;
    }

    // 向量的长度
    len = this.getDimension();

    /**
     * 获取向量的特定元素
     * @param index 目标元素索引
     */
    getItem(index: number): number {
        return this.list[index];
    }

    /**
     * 向量的加法运算
     * @param another 需要进行相加的向量
     */
    add(another: Vector): Vector | string {
        // 向量相加时，要确保其维度与当前维度相等
        if (this.getDimension() === another.getDimension()) {
            const finalList: number[] = [];
            for (let i = 0; i < this.getDimension(); i++) {
                finalList.push(this.getItem(i) + another.getItem(i));
            }
            return new Vector(finalList);
        } else {
            return "维度不相等，无法进行加法运算";
        }
    }

    /**
     * 向量的减法运算
     * @param another
     */
    sub(another: Vector): Vector | string {
        // 与加法运算一样，维度必须相等
        if (this.getDimension() === another.getDimension()) {
            const finalList: number[] = [];
            for (let i = 0; i < this.getDimension(); i++) {
                finalList.push(this.getItem(i) - another.getItem(i));
            }
            return new Vector(finalList);
        } else {
            return "维度不相等，无法进行减法运算";
        }
    }

    /**
     * 向量的数量乘法运算
     * @param K
     */
    mul(K: number): Vector {
        const finalList: number[] = [];
        // 向量的乘法运算规则：用向量的每一个维度去和这个数相乘
        for (let i = 0; i < this.getDimension(); i++) {
            finalList.push(this.getItem(i) * K);
        }

        return new Vector(finalList);
    }

    // 向量取正
    pos(): Vector {
        return this.mul(1);
    }

    // 向量取负
    neg(): Vector {
        return this.mul(-1);
    }

    // 求向量的模
    norm(): number {
        // 对向量的每个维度的值进行平方求和运算
        let sum = 0;
        for (let i = 0; i < this.getDimension(); i++) {
            sum += this.getItem(i) ** 2;
        }
        // 对求出的和进行平方根运算
        return Math.sqrt(sum);
    }

    // 求单位向量
    normalize(): Vector {
        const finalList: number[] = [];
        // 单位向量的求法：用向量中的每个维度的元素除以当前向量的模，将每个求出的元素放进新的向量中，将其返回
        const norm = this.norm();
        if (norm === 0) {
            throw "向量的模不能为0";
        }
        for (let i = 0; i < this.getDimension(); i++) {
            finalList.push(this.getItem(i) / norm);
        }
        return new Vector(finalList);
    }

    // 两个向量的点乘
    dotMul(another: Vector): number | void {
        if (another.getDimension() === this.getDimension()) {
            let final = 0;
            // 两个向量点乘的方法: 将每个向量中的元素互相进行乘法运算，将得到的结果相加
            for (let i = 0; i < this.getDimension(); i++) {
                final += this.getItem(i) * another.getItem(i);
            }
            return final;
        } else {
            console.log("两个向量点乘时其维度必须相等");
        }
    }

    // 零向量
    public static zero(dim: number): Vector {
        return new Vector(new Array(dim).fill(0));
    }

    // 输出向量
    toStr(): string {
        let str = ``;
        for (let i = 0; i < this.list.length; i++) {
            if (i !== this.list.length - 1) {
                str += `${this.list[i]},`;
            } else {
                str += this.list[i];
            }
        }
        return `Vector(${str})`;
    }
}

// 矩阵类
export class Matrix {
    static Vector = Vector;

    constructor(private twoDimArray: number[][]) {}

    /**
     * 矩阵形状
     * @returns [x,y] x行，y列
     */
    shape(): number[] {
        // 矩阵的长度就是矩阵的行数，矩阵中每个向量的维度就是其列数都相等，所以取其第0号元素的作为列数
        return [this.twoDimArray.length, this.twoDimArray[0].length];
    }

    // 获取矩阵行数
    getRowNum(): number {
        return this.shape()[0];
    }

    // 获取矩阵列数
    getColNum(): number {
        return this.shape()[1];
    }

    // 矩阵元素个数
    size(): number {
        const shape = this.shape();
        return shape[0] * shape[1];
    }

    // 矩阵的长度，即：获取矩阵行数
    len = this.getRowNum();

    // 获取矩阵的行向量
    rowVector(index: number): Vector {
        return new Vector(this.twoDimArray[index]);
    }

    // 获取矩阵的列向量
    colVector(index: number): Vector {
        // 存放指定列的元素
        const finalList: number[] = [];
        for (let i = 0; i < this.getRowNum(); i++) {
            // 取出矩阵的每一行
            const row = this.twoDimArray[i];
            // 取出每一行中的指定列，将其存起来
            finalList.push(row[index]);
        }
        // 返回向量
        return new Vector(finalList);
    }

    // 获取矩阵中的元素
    getItem(position: number[]): number {
        return this.twoDimArray[position[0]][position[1]];
    }

    /**
     * 加法运算
     * @param another 另一个矩阵
     * @return Matrix 新的矩阵
     */
    add(another: Matrix): Matrix | void {
        // 两个矩阵相加，其大小必须相等
        if (this.size() === another.size()) {
            const finalList: number[][] = [];
            // 将矩阵的每个元素相加，构成新的矩阵
            for (let i = 0; i < this.getRowNum(); i++) {
                const row: number[] = [];
                for (let j = 0; j < this.getColNum(); j++) {
                    // 相加每个元素
                    row.push(this.getItem([i, j]) + another.getItem([i, j]));
                }
                // 构建新的矩阵
                finalList.push(row);
            }
            return new Matrix(finalList);
        } else {
            console.log("矩阵相加，其大小必须相等");
        }
    }

    /**
     * 减法运算
     * @param another 另一个矩阵
     * @return Matrix 新的矩阵
     */
    sub(another: Matrix): Matrix | void {
        // 两个矩阵相加，其大小必须相等
        if (this.size() === another.size()) {
            const finalList: number[][] = [];
            // 将矩阵的每个元素相加，构成新的矩阵
            for (let i = 0; i < this.getRowNum(); i++) {
                const row: number[] = [];
                for (let j = 0; j < this.getColNum(); j++) {
                    // 相加每个元素
                    row.push(this.getItem([i, j]) - another.getItem([i, j]));
                }
                // 构建新的矩阵
                finalList.push(row);
            }
            return new Matrix(finalList);
        } else {
            console.log("矩阵相减，其大小必须相等");
        }
    }

    /**
     * 矩阵数量乘法
     * @param K 目标值
     * @return Matrix 新的矩阵
     */
    mul(K: number): Matrix {
        const finalList: number[][] = [];
        // 运算规则: 用矩阵中每个向量的元素去乘以目标数量，返回新的矩阵
        for (let i = 0; i < this.getRowNum(); i++) {
            const row: number[] = [];
            for (let j = 0; j < this.getColNum(); j++) {
                row.push(this.getItem([i, j]) * K);
            }
            finalList.push(row);
        }
        // 构建新的矩阵并返回
        return new Matrix(finalList);
    }

    /**
     * 矩阵数量除法
     * @param K 目标值
     * @return Matrix 新的矩阵
     */
    division(K: number): Matrix {
        return this.mul(1 / K);
    }

    /**
     * 创建零矩阵
     * @param r 行数
     * @param c 列数
     * @return Matrix 构建好的矩阵
     */
    static zero(r: number, c: number): Matrix {
        const finalList: number[][] = [];
        for (let i = 0; i < r; i++) {
            const row = [];
            for (let j = 0; j < c; j++) {
                row.push(0);
            }
            finalList.push(row);
        }
        return new Matrix(finalList);
    }

    /**
     * 矩阵与向量相乘
     * @param vector 进行乘法运算的的向量
     * @return Vector 生成的新向量
     */
    mulVector(vector: Vector): Vector | void {
        // 矩阵与向量相乘时其列数必须等于向量的长度
        if (vector.len === this.getColNum()) {
            // 结果数组
            const finalList: number[] = [];
            // 计算规则:
            //    1. 用矩阵的每一项的与向量的每一项做乘法运算
            //    2. 将得到的结果累加
            //    3. 将累加的结果放进结果数组中
            //    4. 根据结果数组构建新的向量
            for (let i = 0; i < this.getRowNum(); i++) {
                // 当前矩阵的行向量与向量进行点运算
                finalList.push(<number>this.rowVector(i).dotMul(vector));
            }
            // 遍历结束，根据结果生成向量，并将其返回
            return new Vector(finalList);
        } else {
            console.log("矩阵与向量相乘时，矩阵的列数必须与向量的长度相等");
        }
    }

    /**
     * 矩阵与矩阵相乘
     * @param matrix
     */
    mulMatrix(matrix: Matrix): Matrix | void {
        // 矩阵A的列数必须和矩阵P的行数相等
        if (this.getColNum() === matrix.getRowNum()) {
            // 存放最终结果的二维数组
            const finalList = [];
            // 拆分两个矩阵，将其拆分成向量与向量之间的点乘
            for (let i = 0; i < this.getRowNum(); i++) {
                // 存放结果行向量的数组
                const resultList: number[] = [];
                // 获取矩阵A的行向量
                const rowVector = this.rowVector(i);
                for (let j = 0; j < matrix.getColNum(); j++) {
                    // 获取矩阵P的列向量
                    const colVector = matrix.colVector(j);
                    // 将行向量与列向量进行点乘，将结果放进结果行向量数组中
                    resultList.push(<number>rowVector.dotMul(colVector));
                }
                // 将构建好的结果行向量放进最终结果的二维数组
                finalList.push(resultList);
            }
            // 根据最终结果的二维数组构建矩阵
            return new Matrix(finalList);
        } else {
            console.log("矩阵与矩阵相乘，其中一个矩阵的列数必须与另一个矩阵的行数相等");
        }
    }

    // 矩阵取正
    pos(): Matrix {
        return this.mul(1);
    }

    // 矩阵取负
    neg(): Matrix {
        return this.mul(-1);
    }
    // 输出矩阵
    toStr(): string {
        return `Matrix(${this.twoDimArray})`;
    }
}